﻿#region Using directives

using System;
using System.Text;
using System.CodeDom;
using System.Collections.Generic;

using Weazel.Gezel.Model;
using Weazel.Gezel.Conversions.CodeGeneration;

#endregion

namespace Weazel.Gezel.Conversions.Model
{
  public class OutPortType : SignalTypeBase
  {
    private Field colorField;

    private Method getControlStepMethod;

    private readonly OutPort outport;
    public OutPort OutPort
    {
      get { return outport; }
    }

    public OutPortType(OutPort outport, ControllerType controllerType, SoftwareModel softwareModel)
      : base(String.Format("OutPort_{0}", outport.Name.Replace(".", "_")), outport, controllerType, softwareModel, outport.Width, outport.Signed)
    {
      this.outport = outport;
      this.BaseTypes.Add(new CodeTypeReference("IOutport"));

      this.comments["summary"] = string.Empty;

      valueField.Comments.Add(new CodeCommentStatement("Caches the outport value."));

      buildInitializerMethod();

      if (softwareModel.Language == TargetLanguage.IsoCpp)
      {

        // we'll have to initialize the field aswell since it is a class and not an enum in C++
        initializeMethod.Statements.Add(new CodeSnippetStatement("    _Color = Color::White;"));
      }
      else
      {
        colorField = AddField("color", "Color"); // notice different casing
        colorField.Comments.Add(new CodeCommentStatement("Used for depth-first-search on outports"));

        // managed code.. use property to access color
        CodeMemberProperty property = new CodeMemberProperty();
        property.Attributes = MemberAttributes.AccessMask & MemberAttributes.Public;
        property.Name = "_Color";
        property.Type = new CodeTypeReference("Color");
        property.HasGet = true;
        property.GetStatements.Add(
          new CodeMethodReturnStatement(
            new CodeFieldReferenceExpression(
              new CodeThisReferenceExpression(),
              "color"
            )
          )
        );
        property.HasSet = true;
        property.SetStatements.Add(
          new CodeAssignStatement(
            new CodeFieldReferenceExpression(
              new CodeThisReferenceExpression(),
              "color"),
            new CodeVariableReferenceExpression("value")
          )
        );
        Members.Add(property);
      }
    }

    public void PostAnalyzeDependencies()
    {
      if (controllerType != null)
      {
        foreach (ExecutionSet sfgSet in controllerType.ExecutionSets)
        {
          Field array;// = AddField(string.Format("DependentOutports_ControlStep_{0}", sfgSet.ControlStepId), "IOutport[]");

          if (softwareModel.Language != TargetLanguage.IsoCpp)
          {
            array = AddField(string.Format("DependentOutports_ControlStep_{0}", sfgSet.ControlStepId), "IOutport[]");
          }
          else
          {
						array = AddField(string.Format("DependentOutports_ControlStep_{0}", sfgSet.ControlStepId), "OutportDynamicList");
          }

          int controlStep = sfgSet.ControlStepId;


          List<OutPortType> dependencies = new List<OutPortType>();
          dependencies.AddRange(this.OutportDependencies[controlStep]);

          foreach (RegisterType registerType in this.RegisterDependencies[controlStep])
          {
            foreach (OutPortType outportType in registerType.OutportDependencies[controlStep])
            {
              if (!dependencies.Contains(outportType))
                dependencies.Add(outportType);
            }
          }
          foreach (SignalType signalType in this.SignalDependencies[controlStep])
          {
            foreach (OutPortType outportType in signalType.OutportDependencies[controlStep])
            {
              if (!dependencies.Contains(outportType))
                dependencies.Add(outportType);
            }
          }

          addDependencyArrayInitializer(array, dependencies);
        }


        // add statement to update cycle field
        evaluateMethod.Statements.Add(Statement.Empty);
        evaluateMethod.Statements.Add(
          new CodeCommentStatement("Mark this entity as valid for this cycle ")
        );
        evaluateMethod.Statements.Add(
          new CodeAssignStatement(
            new CodeFieldReferenceExpression(
              new CodeThisReferenceExpression(),
              cycleField.Name
            ),
            new CodeFieldReferenceExpression(
              new CodeTypeReferenceExpression("Simulator"),
              "CurrentCycle"
            )
          )
        );
      }
      buildGetDependentOutPortsMethod();
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="array"></param>
    /// <param name="dependencies"></param>
    /// <returns>A reference to the array being initialized</returns>
    private CodeFieldReferenceExpression addDependencyArrayInitializer(Field array, List<OutPortType> dependencies)
    {
      CodeFieldReferenceExpression arrayRef =
        new CodeFieldReferenceExpression(
          new CodeThisReferenceExpression(),
          array.Name
        );

      if (softwareModel.Language != TargetLanguage.IsoCpp)
      {
        // produces the code:
        // this.myArray = new IOutport[size];

        CodeArrayCreateExpression arrayCreate = new CodeArrayCreateExpression("IOutport");

        initializeMethod.Statements.Add(new CodeAssignStatement(arrayRef, arrayCreate));

        // add statements which fills the array with correct data
        foreach (OutPortType outportType in dependencies)
        {
          CodeFieldReferenceExpression outportDependency =
            new CodeFieldReferenceExpression(
              new CodeTypeReferenceExpression("Simulator"),
              outportType.InstanceName
            );

          arrayCreate.Initializers.Add(outportDependency);
        }
      }
      else
      {
        CodeSnippetStatement vectorCreate =
          new CodeSnippetStatement(
            string.Format(
							"{0} = new OutportDynamicList();",
              array.Name
            )
          );

        initializeMethod.Statements.Add(vectorCreate);

        // add statements which fills the vector with correct data
        int dependencyIndex = 0;
        foreach (OutPortType outportType in dependencies)
        {
          CodeFieldReferenceExpression outportDependency =
            new CodeFieldReferenceExpression(
              new CodeTypeReferenceExpression("Simulator"),
              outportType.InstanceName
            );

          CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression(arrayRef, "push_back", outportDependency);
          initializeMethod.Statements.Add(methodInvoke);
        }
      }
      return arrayRef;
    }

    private void buildGetDependentOutPortsMethod()
    {
      getControlStepMethod = AddMethod("GetDependentOutports");
      
      // set proper return type
      if (softwareModel.Language != TargetLanguage.IsoCpp)
        getControlStepMethod.ReturnType = new CodeTypeReference(new CodeTypeReference("IOutport"), 1);
      else
				getControlStepMethod.ReturnType = new CodeTypeReference("OutportDynamicList");
      CodeStatementCollection statements = getControlStepMethod.Statements;

      if (controllerType == null)
      {
				Field emptyDependenciesField = AddField("NoDependencies", softwareModel.Language == TargetLanguage.IsoCpp ? "OutportDynamicList" : "IOutport[]");
        CodeFieldReferenceExpression emptyDependenciesFieldRef = addDependencyArrayInitializer(emptyDependenciesField, new List<OutPortType>());
        getControlStepMethod.Statements.Add(new CodeMethodReturnStatement(emptyDependenciesFieldRef));
        return;
      }
      else
      {

        CodeSnippetStatement switchStatement = null;
        switch (softwareModel.Language)
        {
          case TargetLanguage.CSharp:
            switchStatement = new CodeSnippetStatement(
              string.Format("      switch(Simulator.{0}.{1})", controllerType.InstanceName, controllerType.CurrentControlStep.Name)
            );
            break;
          case TargetLanguage.VisualC:
          case TargetLanguage.IsoCpp:
            switchStatement = new CodeSnippetStatement(
              string.Format("      switch(Simulator::{0}->{1})", controllerType.InstanceName, controllerType.CurrentControlStep.Name)
            );
            break;
          case TargetLanguage.VisualBasic:
          case TargetLanguage.JSharp:
          default:
            throw new ApplicationException("Unsupported language");
        }

        System.Diagnostics.Debug.Assert(switchStatement != null);
        statements.Add(switchStatement);
        statements.Add(new CodeSnippetStatement("      {"));

        foreach (ExecutionSet sfgSet in controllerType.ExecutionSets)
        {
          statements.Add(new CodeSnippetStatement(string.Format("      case {0}:", sfgSet.ControlStepId)));
          string fieldName = string.Format("DependentOutports_ControlStep_{0}", sfgSet.ControlStepId);
          CodeFieldReferenceExpression fieldRef = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName);
          CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement(fieldRef);
          statements.Add(returnStatement);
        }

        statements.Add(new CodeSnippetStatement("      default:"));
        CodePrimitiveExpression messageExpression = new CodePrimitiveExpression("Internal Error");
        CodeObjectCreateExpression createExpression = new CodeObjectCreateExpression("ApplicationException", messageExpression);

        statements.Add(new CodeThrowExceptionStatement(createExpression));

        statements.Add(new CodeSnippetStatement("      }"));
      }
    }
  }
}
